Major lljson rework to make round-trippable serialization easier#61
Major lljson rework to make round-trippable serialization easier#61HaroldCindy wants to merge 11 commits intomainfrom
Conversation
Of note, there's now a notion of replacer and reviver callbacks as in JS' `JSON` APIs. An example of their use is in the tests folder as `lljson_typedjson.lua`. We went with this form since it allows constructing a different representation of the object before serializing without requiring you to construct an entire, serializable copy before calling `lljson.encode()`. That allows you to save memory, since the serializable version of each object only need to be alive as long as we're still traversing the object. Additionally, an empty table is now encoded as `[]` by default. This is probably the most common meaning for an empty table, but you can also apply `object_mt` as a metatable or add `__jsontype="object"` to your own metatable to force serialization as an object. Similarly, `array_mt` or `__jsontype="array"` will act as a hint to treat your object as an array. `__len` should no longer be used as a hint that the object should be treated as an array, that's what `__jsontype` is for. Also added a new options table format to `lljson.encode()` and friends. The table now allows you to specify that `__tojson` hooks should be skipped, so you can manually invoke them at your leisure in your replacer hooks.
| @@ -1 +1 @@ | |||
| /* Lua CJSON - JSON support for Lua | |||
There was a problem hiding this comment.
I'm learning how it works, is it like this? in the most metatably case?
table1
if replacer then
table2 = table1.__tojson
table3 = replacer
end
shape = table3.__jsontype
table4 = table3.__tojson
if array then
len = table4.__len
end
- A table can be replaced 3 times (
__tojsoncalled in replacer, the replacer and__tojsonin serialization). - If replacer returns the same table, its
__tojsonis called twice (in replacer and in serialization). - In serialization,
__tojsonreturning a table with__jsontypehas no effect.
There was a problem hiding this comment.
it looks like you are correct. __tojson can be called both before and after the replacer:
> tojson = { __tojson = function(self, ...) print("tojson", self, ...); return self end }
> function replacer(...) print("replacer", ...); return setmetatable({}, tojson) end
> lljson.encode(setmetatable({}, tojson), {replacer=replacer})
tojson table: 0x00000000398548a0 table: 0x0000000039854830
replacer nil table: 0x00000000398548a0 nil
tojson table: 0x0000000039854788 table: 0x0000000039854830
[]
There was a problem hiding this comment.
it looks like __jsontype is indeed ignored on tables returned from __tojson:
> lljson.encode(setmetatable({}, {__jsontype="object"}))
{}
> lljson.encode(setmetatable({}, {__tojson = function() return setmetatable({}, {__jsontype="object"}) end }))
[]
There was a problem hiding this comment.
Correct, __jsontype is what determines the encoding semantic for the object, __tojson() is a hook that objects can use to present their own view of the data to the serializer. Will look at that double __tojson call though.
There was a problem hiding this comment.
Regarding __jsontype and __tojson, a theoretical (and probably non-existent case) would be a __tojson that returns, depending on the data, any one of:
- a JSON object
- a JSON array
- an SLua array to be encoded as a JSON object
Then, we would need to set __jsontype in __tojson and use a replacer that does nothing ( replacer = function(_, v) return v end ) to get __tojson called and __jsontype set before it is used.
There was a problem hiding this comment.
Okay, fixed the double __tojson call in the replacer case, __tojson should only ever be called once, and before the replacer as is the case in JSON.stringify() in JS. Still not entirely sure what to do with __jsontype though...
Co-authored-by: Tapple Gao <tapplek@gmail.com>
This helps improve round-trippability of JSON payloads from outside SL, preserving the `array`-ness or `object`-ness of empty tables especially.
Of note, there's now a notion of replacer and reviver callbacks as in JS'
JSONAPIs. An example of their use is in the tests folder aslljson_typedjson.lua.We went with this form since it allows constructing a different representation of the object before serializing without requiring you to construct an entire, serializable copy before calling
lljson.encode(). That allows you to save memory, since the serializable version of each object only need to be alive as long as we're still traversing the object.Additionally, an empty table is now encoded as
[]by default. This is probably the most common meaning for an empty table, but you can also applyobject_mtas a metatable or add__jsontype="object"to your own metatable to force serialization as an object. Similarly,array_mtor__jsontype="array"will act as a hint to treat your object as an array.__lenshould no longer be used as a hint that the object should be treated as an array, that's what__jsontypeis for.Also added a new options table format to
lljson.encode()and friends. The table now allows you to specify that__tojsonhooks should be skipped, so you can manually invoke them at your leisure in your replacer hooks.